Bug 162

Bug Description:

This is a capacity leak bug.
The expected exception is java.util.NoSuchElementException: Timeout waiting for idle object.
More details about this bug are at POOL-162 JIRA page.

Interleaving Description:

org.apache.commons.pool.impl.GenericObjectPool:

t1 t2 public Object borrowObject() throws Exception {
...
1 12 _allocationQueue.add(latch);
13 allocate();
...
switch(whenExhaustedAction) {
...
case WHEN_EXHAUSTED_BLOCK:
try {
synchronized (latch) {
if(maxWait <= 0) {
2 14 latch.wait();
}
...
}
}
catch(InterruptedException e) {
Thread.currentThread().interrupt();
3 throw e;
}
if(maxWait > 0 && ((System.currentTimeMillis() - starttime) >= maxWait)) {
throw new NoSuchElementException("Timeout waiting for idle object");
}
...

}

private synchronized void allocate() {
if (isClosed()) return;

// First use any objects in the pool to clear the queue
for (;;) {
8 if (!_pool.isEmpty() && !_allocationQueue.isEmpty()) {
Latch latch = (Latch) _allocationQueue.removeFirst();
9 latch.setPair((ObjectTimestampPair) _pool.removeFirst());
10 _numInternalProcessing++;
synchronized (latch) {
11 latch.notify();
}
} else {
break;
}
}

// Second utilise any spare capacity to create new objects

}

public void returnObject(Object obj) throws Exception {
try {
4 addObjectToPool(obj, true);
} catch (Exception e) {
...
}
}

private void addObjectToPool(Object obj, boolean decrementNumActive) throws Exception {
...
if (_lifo) {
5 _pool.addFirst(new ObjectTimestampPair(obj));
} else {
_pool.addLast(new ObjectTimestampPair(obj));
}
if (decrementNumActive) {
6 _numActive--;
}
7 allocate();
...
}

Precondition: _maxActive is 1, and 1 object has been borrowed from pool. Pool is empty.

a) thread 1 borrows an object, and associated latch is added into _allocationQueue at 1.
b) thread 1 waits at 2, because pool is empty.
c) thread 1 is interrupted, it throws an exception at 3 and terminates.
d) thread 2 returns the borrowed object to pool. It executes 4 and 5. There is one idle object in pool now.
e) active number is decreased at 6, _numActive is 0 now.
f) thread 2 calls allocate() at 7 and comes to 8. _pool is not empty due to step d), _allocationQueue is not empty due to step a), and _allocationQueue is not cleared at step c).
g) the idle object in pool is associated with the thread 1's dead latch at 9. This object could never be returned to pool again and this is memory leak.
h) _numInternalProcessing is added to 1 at 10.
i) The execution of 11 has no effects, because the waiting thread is already interrupted and at step c).
j) thread 2 goes through 12 and 13 to borrow an object from pool. But the only object in pool has been associated to the dead thread1 at step g), so thread 2 will wait forever at 14 and no one can notify it.

How To Reproduce:

This bug is reproduced under pool 1.5 and JDK 1.6.0_33.
Execute the following scripts to run the test to reproduce the bug (assume the location of the pool test project is pool_test_home).

Linux:
${pool_test_home}/scripts/162.sh
Windows:
%pool_test_home%\scripts\162.bat